/**
 * Server.cpp
 * Greg Steltenpohl & Cory Bryant
 */

#include <arpa/inet.h>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <vector>

#include "Blowfish.h"
#include "Server.h"
#include "Packet.h"
#include "PracticalSocket.h"

using namespace std;

int packetSize;     // Longest string to echo
char* filePath;
bool encryption = true;
int recvWinSize; // 1 = gbn or sw, >1 sr. Always < senderWinSize
int maxSeqNum;
int port;
short expectedSeqNum = 0;
short currPackNum = 0;
vector<Packet*> window(recvWinSize, NULL);

char* buffer;
ifstream myFile;
Blowfish bf;

char* keyS;

string sourceAddress;
unsigned short sourcePort;
bool eofFound = false;
bool finished = false;


void fileServer();

struct ThreadStart {
	TCPSocket* socket;
	Server* server;
};

/*
 * Print server id.
 */
void printServerID(Server* server) {
	// Print my idB.
	char* idB = new char[INNER_BUFFER_LENGTH];
	memset(idB, 0, INNER_BUFFER_LENGTH);
	// Get host name.
	char* myHostName = new char[INNER_BUFFER_LENGTH];
	memset(myHostName, 0, INNER_BUFFER_LENGTH);
	gethostname(myHostName, INNER_BUFFER_LENGTH);
	// Get ip address.
	char* ipAddress = inet_ntoa(*(in_addr*)gethostbyname(myHostName)->h_addr);
	// Remove periods.
	char* ip1 = strtok(ipAddress, ".");
	char* ip2 = strtok(NULL, ".");
	char* ip3 = strtok(NULL, ".");
	char* ip4 = strtok(NULL, ".");
	// Get PID.
	int pid = getpid();
	// Append all togeather.
	sprintf(idB, "%d%s%s%s%s", pid, ip1, ip2, ip3, ip4);
	// Print out my idB.
	cout << "My idB is " << idB << endl;
	server->setIdB(idB);
	// Free Resources.
	delete[] myHostName;
}

/*
 * Server threadMain.
 * Main program of a thread.
 * Modified from PracticalSocket's TCPEchoServer-Thread.cpp example.
 */
void* threadMain(void* threadStart) {
	// Make sure thread resources are deallocated upon return.
	pthread_detach(pthread_self());
	ThreadStart* threadStartPointer = (ThreadStart*) threadStart;
	ThreadStart threadStartStruct = *threadStartPointer;
	TCPSocket* socket = threadStartStruct.socket;
	Server* server = threadStartStruct.server;
	// Extract socket file descriptor from argument.
	(*server).handleTCPRequest(socket);
	delete(TCPSocket*) socket;
	return NULL;
}

int main(int argc, char *argv[]) {
	ifstream starterFile ("Start.txt"); // Starter file reading from.
	char* line = new char[256]; // Line in file.
	memset(line, 0, 256);
	char* token = new char[256]; // First token of line.
	memset(token, 0, 256);
	char* delimiter = "="; // Tokenizer delimiter.
	char* keyB;
	char* nonceB;
	Server* server; 
	// Get from file.
	if (starterFile.is_open()) {
		// Get next line with "encryption".
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "encryption"));
		// Get encryption.
		encryption = atoi(strtok(NULL, delimiter));
		
		// Get next line with "protocol".
		char* protocol = new char[INNER_BUFFER_LENGTH];
		memset(protocol, 0, INNER_BUFFER_LENGTH);
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "protocol"));	
		// Get kdcHostName.
		sprintf(protocol, "%s", strtok(NULL, delimiter));
		// Get next line with "sequenceNumberRange".
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "sequenceNumberRange"));
		// Get sequenceNumberRange.
		int sequenceNumberRange = atoi(strtok(NULL, delimiter));
		maxSeqNum = sequenceNumberRange;
		// Get next line with "packetSize".
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "packetSize"));
		// Get packetSize.
		packetSize = atoi(strtok(NULL, delimiter)) + PADDING_SIZE;
		// Get next line with "timeoutInterval".
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "timeoutInterval"));
		// Get timeoutInterval.
		int timeoutInterval = atoi(strtok(NULL, delimiter));
		// Get my server number from user.
		char* serverNumber = new char[4];
		memset(serverNumber, 0, 4);
		cout << "Please enter the server number for this server.\n>";
		cin >> serverNumber;
		// Get next line with numberOfServers
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "numberOfServers"));
		// Get next line with "serverNumber=serverNumber".
		int serverNumberBool = 0;
		do {
			serverNumberBool = 0;
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
			if (!strcmp(token, "serverNumber")) {
				serverNumberBool = 1;
				token = strtok(NULL, delimiter);
			}
		} while (!(serverNumberBool == 1 && atoi(token) == atoi(serverNumber)));
		delete[] serverNumber;
		// Get next line with "port"
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "port"));
		// Get port.
		port = atoi(strtok(NULL, delimiter));
		// Get next line with "keyB"
		keyB = new char[INNER_BUFFER_LENGTH];
		memset(keyB, 0, INNER_BUFFER_LENGTH);
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "keyB"));
		// Get keyB.
		sprintf(keyB, "%s", strtok(NULL, delimiter));
		// Get next line with "nonceB"
		nonceB = new char[INNER_BUFFER_LENGTH];
		memset(nonceB, 0, INNER_BUFFER_LENGTH);
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "nonceB"));
		// Get nonceB.
		sprintf(nonceB, "%s", strtok(NULL, delimiter));
		// Get next line with "windowSize"
		do {
			starterFile.getline(line, 256);
			token = strtok(line, delimiter);
		} while (strcmp(token, "windowSize"));
		// Get windowSize.
		int windowSize = atoi(strtok(NULL, delimiter));
		recvWinSize = windowSize;
		// Create server object.
		server = new Server(encryption, protocol, sequenceNumberRange, packetSize, timeoutInterval, keyB, nonceB, port, windowSize);
		// Close file.
		starterFile.close();
	} else {
		cout << "FILE ERROR" << endl;
		exit(1);
	}

	// Print IDB.
	printServerID(server);
	
	try {
		TCPServerSocket serverSocket(server->getPort()); // Socket descriptor for server.
		for (;;) { // Run forever.
			// Create separate memory for client argument.
			TCPSocket* clientSocket = serverSocket.accept();
			// Create client thread.
			pthread_t threadID; // Thread ID from pthread_create().
			ThreadStart threadStart;
			threadStart.socket = clientSocket;
			threadStart.server = server;
			if (pthread_create(&threadID, NULL, threadMain, (void*) &threadStart) != 0) {
				cerr << "Unable to create thread" << endl;
				exit(1);
			}
		}
		// Destructor closes the socket
	} catch (SocketException &e) {
		cerr << e.what() << endl;
		exit(1);
	}
	return 0;
}

/*
 * Server handleTCPRequest.
 * TCP client handling function.
 * Modified from PracticalSocket's TCPEchoServer-Thread.cpp example.
 */
void Server::handleTCPRequest(TCPSocket* socket) {
	// Get handshake from socket.
	char* handshakeBuffer = new char[MAX_BUFFER_LENGTH];
	memset(handshakeBuffer, 0, MAX_BUFFER_LENGTH);
	socket->recv(handshakeBuffer, MAX_BUFFER_LENGTH);
	Blowfish blowfish;
	if (encryptionOn == 1) {
		blowfish.Set_Passwd(keyB);
		blowfish.Decrypt(handshakeBuffer, (!(MAX_BUFFER_LENGTH % 8 == 0) ? MAX_BUFFER_LENGTH + 8 - (MAX_BUFFER_LENGTH % 8) : MAX_BUFFER_LENGTH));	
	}
	keyS = new char[INNER_BUFFER_LENGTH];
	memset(keyS, 0, INNER_BUFFER_LENGTH);
	char* idA = new char[INNER_BUFFER_LENGTH];
	memset(idA, 0, INNER_BUFFER_LENGTH);
	// TODO: Thread safe.
	pthread_mutex_lock(&parseMutex);
	// Get keyS
	sprintf(keyS, "%s", strtok(handshakeBuffer, "|"));
	// Get idA
	sprintf(idA, "%s", strtok(NULL, "|"));
	pthread_mutex_unlock(&parseMutex);
	cout << "Recd from " << idA << ":\n\tE(Kb)[Ks|idA|]\n\t" << keyS << "|" << idA << "|" << endl;
	delete[] handshakeBuffer;
	// Send nonce to ida
	char* replyBuffer = new char[INNER_BUFFER_LENGTH];
	memset(replyBuffer, 0, INNER_BUFFER_LENGTH);
	sprintf(replyBuffer, "%s", nonceB);
	if (encryptionOn == 1) {
		blowfish.Set_Passwd(keyS);
		blowfish.Encrypt(replyBuffer, (!(INNER_BUFFER_LENGTH % 8 == 0) ? INNER_BUFFER_LENGTH + 8 - (INNER_BUFFER_LENGTH % 8) : INNER_BUFFER_LENGTH));	
	}
	socket->send(replyBuffer, INNER_BUFFER_LENGTH);
	cout << "Sent to " << idA << ":\n\tE(Ks)[nonceB]\n" << nonceB << endl;
	delete[] replyBuffer;
	char* finalBuffer = new char[INNER_BUFFER_LENGTH];
	memset(finalBuffer, 0, INNER_BUFFER_LENGTH);
	socket->recv(finalBuffer, INNER_BUFFER_LENGTH);
	if (encryptionOn == 1) {
		blowfish.Set_Passwd(keyS);
		blowfish.Decrypt(finalBuffer, (!(INNER_BUFFER_LENGTH % 8 == 0) ? INNER_BUFFER_LENGTH + 8 - (INNER_BUFFER_LENGTH % 8) : INNER_BUFFER_LENGTH));	
	}
	cout << "Recd from " << idA << ":\n\tE(Ks)[f(nonceB)]\n\t" << finalBuffer << endl;
	long fNonce = (long) atoi(finalBuffer);
	cout << nonceB << endl;
	cout << "\tf(nonceB)" << fNonceB << endl;
	if (fNonce == fNonceB) {
		cout << "Validation successful" << endl;
	} else {
		cout << "Validation failed" << endl;
	}
	delete[] finalBuffer;
	filePath = new char[128];
	memset(filePath, 0, 128);
	socket->recv(filePath, 128);
	char* totalFileSize = new char[128];
	memset(filePath, 0, 128);
	socket->recv(filePath, 128);
	// Start ftp.

	fileServer();
}

void fileServer() {
        unsigned short serverPort = port;     // First arg:  local port

        bf.Set_Passwd(keyS);

        try {
                UDPSocket sock(serverPort);

                int recvMsgSize;                  // Size of received message

                ofstream myFile;
                // @TODO File needs to be entered in from prompt
                myFile.open(filePath, fstream::out | fstream::binary | fstream::trunc);
				buffer = new char[packetSize];
                while (!finished) {  // Run forever
                        memset(buffer, 0, packetSize);
                        // Block until receive message from a client
                        recvMsgSize = sock.recvFrom(buffer, packetSize, sourceAddress, sourcePort);

                        Packet* pb = new Packet(buffer, packetSize, &bf);
                        short seqNum = pb->seqNum;
                        short packNum = pb->packetNum;
                        cout << "Received packet from " << sourceAddress << ":" << sourcePort << endl;
                        cout << "Seq: " << seqNum << " recieved" << endl;

                        if (pb->checkCrc()) {
                                cout << "Start: " << expectedSeqNum << " End: " << (expectedSeqNum + recvWinSize - 1) % maxSeqNum << endl;
                                if (seqNum >= expectedSeqNum && seqNum <= expectedSeqNum + recvWinSize - 1) { // In the window
                                        cout << "In the window" << endl;
                                        int index = seqNum % recvWinSize;
                                        if (window.at(index) != NULL) {
                                                // Duplicate packet, only send ack
                                                // Don't think this can happen but just in case
                                                cout << "Duplicate packet " << seqNum << endl;
                                        } else {
                                                // Buffer the packet
                                                window.at(index) = pb;

                                                // Only if you are the first in the window
                                                if (seqNum % recvWinSize == expectedSeqNum % recvWinSize) {
                                                        index = expectedSeqNum % recvWinSize;
                                                        while (window.at(index) != NULL) {
                                                                // Move Window
                                                                cout << "Next window: " << (expectedSeqNum + 1) % maxSeqNum << " - " << (((expectedSeqNum + 1) % maxSeqNum) + recvWinSize - 1) % maxSeqNum << endl;
                                                                expectedSeqNum = (expectedSeqNum + 1) % maxSeqNum;
                                                                currPackNum++;

                                                                // Write to file
                                                                if (encryption) window.at(index)->decryptPayload(sourceAddress);
                                                                if (myFile.is_open()) {
                                                                        myFile.write(window.at(index)->getPayload(), window.at(index)->getPayloadSize());
                                                                        myFile.flush();
                                                                }

                                                                // Reset packet
                                                                delete window.at(index);
                                                                window.at(index) = NULL;

                                                                index = expectedSeqNum % recvWinSize;
                                                        }
                                                }
                                        }

                                        // Send ack
                                        sock.sendTo(pb->buildAck(), packetSize, sourceAddress, sourcePort);
                                        cout << "Ack " << seqNum << " sent" << endl;
                                } else if (packNum < currPackNum) { // Behind you, send ack only
                                        cout << "Behind the window" << endl;
                                        sock.sendTo(pb->buildAck(), packetSize, sourceAddress, sourcePort);
                                        cout << "Ack " << seqNum << " sent" << endl;
                                } // Ignore ahead of you
                        } else {
                                cout << "Corrupt packet!" << endl;
                        }

                        // @TODO All packets need to be cleared thru before ending!
                        if (pb->isEof()) {
                                finished = true;
                        }
/*
                        if (eofFound) {
                                finished = true;
                                for (unsigned int i = 0; i < window.size(); i++) {
                                        if (window.at(i) != NULL) {
                                                finished = false;
                                        }
                                }
                        }
*/
                }

                myFile.close();
        } catch (SocketException &e) {
                cerr << e.what() << endl;
                exit(1);
        }


}

